{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parte 8 - Introdução a Planos\n",
    "\n",
    "\n",
    "### Contexto \n",
    "\n",
    "Aqui nós introduzimos um objeto que é crucial para escalar a indústria do Aprendizado Federado: Planos. Isso reduz dramaticamente o uso de largura de banda, permite esquemas assíncronos e da mais autonomia para dispositivos remotos. O conceito original do plano pode ser encontrado no artigo [Towards Federated Learning at Scale: System Design](https://arxiv.org/pdf/1902.01046.pdf), porém com algumas adaptações para as nossas necessidades no PySyft.\n",
    "\n",
    "Um plano tem a intenção de salvar uma sequência de operações do _torch_ , como uma função, mas permitindo enviar essa sequência de operações para um worker remoto e manter a referência dele. Dessa forma, para computar remotamente essa sequência de $n$ operações em alguma entrada referenciada através desses ponteiros, ao invés de mandar $n$ mensagens, você precisa agora enviar apenas uma única mensagem com as referências para o plano e para os ponteiros. Você pode também fornecer tensores com sua função (que chamamos _state tensors_ ) para ter as funcionalidades extendidas. Planos podem ser vistos tanto como uma função que você pode enviar, como uma classe que também pode ser enviada e executada remotamente. Portanto, para usuários de alto nível, a noção de plano desaparece e é substituída por um recurso mágico que permite enviar para workers remotos funções arbitrárias que contém uma sequência de funções _torch_ .\n",
    "\n",
    "Uma coisa a se notar é que a classe de funções que você quer transformar em planos é atualmente limitada para sequências de operações _hook_ (gancho) do _torch_ , exclusivamente. Em particular, isso exclui estruturas lógicas como `if`, `for` e `while`, mesmo que estejamos trabalhando para ter soluções em breve. _Para ser completamente preciso, você pode usá-los, mas o caminho lógico que você toma (primeiro `if` é falso e 5 laços `for`, por exemplo) na primeira computação do seu plano será levado por todas as próximas computações, o qual queremos, na maioria dos casos, evitar._\n",
    "\n",
    "Autores:\n",
    "- Théo Ryffel - Twitter [@theoryffel](https://twitter.com/theoryffel) - GitHub: [@LaRiffle](https://github.com/LaRiffle)\n",
    "- Bobby Wagner - Twitter [@bobbyawagner](https://twitter.com/bobbyawagner) - GitHub: [@robert-wagner](https://github.com/robert-wagner)\n",
    "- Marianne Monteiro - Twitter [@hereismari](https://twitter.com/hereismari) - GitHub: [@mari-linhares](https://github.com/mari-linhares)\t\n",
    "\n",
    "Tradutor:\n",
    "- João Lucas - GitHub: [@joaolcaas](https://github.com/joaolcaas) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Importações e especificações do modelo\n",
    "\n",
    "Primeiro faremos as importações oficiais."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "E então aqueles específicos do PySyft, com uma importante observação: **o worker local não deve ser um worker cliente**. Workers não clientes podem salvar objetos e precisamos dessa funcionalidade para usar um plano."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import syft as sy  # importe a biblioteca PySyft\n",
    "hook = sy.TorchHook(torch)  # hook PyTorch i.e. adiciona funcionalidades extras \n",
    "\n",
    "# IMPORTANTE: worker local não deve ser um worker cliente\n",
    "hook.local_worker.is_client_worker = False\n",
    "\n",
    "\n",
    "server = hook.local_worker"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para ser consistente com o conceito fornecido no artigo referenciado, definimos _workers_ remotos ou devices, provendo eles com algum dado. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x11 = torch.tensor([-1, 2.]).tag('input_data')\n",
    "x12 = torch.tensor([1, -2.]).tag('input_data2')\n",
    "x21 = torch.tensor([-1, 2.]).tag('input_data')\n",
    "x22 = torch.tensor([1, -2.]).tag('input_data2')\n",
    "\n",
    "device_1 = sy.VirtualWorker(hook, id=\"device_1\", data=(x11, x12)) \n",
    "device_2 = sy.VirtualWorker(hook, id=\"device_2\", data=(x21, x22))\n",
    "devices = device_1, device_2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exemplo Básico\n",
    "\n",
    "Vamos definir uma função que queremos transformar em um plano. Para isso, apenas adiciona-se um _decorator_ (decorador) acima da definição da função."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@sy.func2plan()\n",
    "def plan_double_abs(x):\n",
    "    x = x + x\n",
    "    x = torch.abs(x)\n",
    "    return x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos verificar.\n",
    "Sim, agora nós temos um plano!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plan_double_abs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para usar um plano, precisamos de duas coisas: contruir um plano _(i.e. registrar uma sequência de operações presentes na função)_ e mandar isso para um worker / device. Felizmente você pode fazer isso de uma maneira muito fácil.\n",
    "\n",
    "#### Construindo um plano\n",
    "\n",
    "Para construir um plano, você só precisa chamar isso em algum dado.\n",
    "\n",
    "Vamos primeiro pegar a referência para algum dado remoto: uma requisição é enviada para a rede e o ponteiro de referência é retornado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_data = device_1.search('input_data')[0]\n",
    "pointer_to_data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se dissermos para um plano que ele deve ser executado remotamente no dispositivo `location:device_1`, isso retornará um erro pois o plano não foi construído ainda."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plan_double_abs.is_built"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Falha acontece se mandar um plano não construído\n",
    "try:\n",
    "    plan_double_abs.send(device_1)\n",
    "except RuntimeError as error:\n",
    "    print(error)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para construir um plano só precisamos chamar `build` no plano e passar os argumentos necessários para a execução de um plano (a.k.a algum dado). Quando um plano é construído, todos os comandos são executados sequencialmente por um worker local, capturados pelo plano e então salvos no atributo `actions`!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plan_double_abs.build(torch.tensor([1., -2.]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plan_double_abs.is_built"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Agora, se tentarmos enviar um plano, irá funcionar!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Essa célula é executada com sucesso\n",
    "pointer_plan = plan_double_abs.send(device_1)\n",
    "pointer_plan"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Como nos tensores, podemos ver um ponteiro para o objeto que foi enviado, sendo chamado simplesmente de `PointerPlan`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Um lembrete importante é que, quando um plano é construído, nós de antemão pré-definimos os id(s) dos resultados das computações que devem ser salvos. Isso irá permitir enviar os comandos de forma assíncrona, já ter uma referência para um resultado virtual e continuar as computações locais sem ter que esperar os resultados remotos serem computados. Uma aplicação importante é quando você exige a computação de um grupo no device_1 e não quer esperar pelo fim dessa computação para mandar outro grupo de computação para o device_2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Executando um Plano Remotamente\n",
    "\n",
    "Nós agora podemos executar um plano remotamente chamando um ponteiro para um plano com um ponteiro para algum dado. Isso emite um comando para executar esse plano remotamente, sendo assim, a localização pré-definida da saída do plano agora contém o resultado (lembre-se que  pré-definimos a localização do resultado antes da computação). Isso também requer uma única rodada de comunicação.\n",
    "\n",
    "O resultado é simplesmente um ponteiro, assim como quando você chama uma função do gancho no _torch_ ."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_result = pointer_plan(pointer_to_data)\n",
    "print(pointer_to_result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "E você pode simplesmentes pegar o valor de volta."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_result.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Rumo a um exemplo concreto\n",
    "\n",
    "Mas nós queremos que Planos sejam aplicados para aprendizado profundo e federado, certo? Então vamos dar uma olhada em um exemplo ligeiramente mais complicado, usando rede neural que certamente você deve estar ansioso para usar.\n",
    "Note que estamos agora transformando uma classe em um Plano. Para isso, nós herdamos a nossa classe de sy.Plan (ao invés de herdar de nn.Module)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Net(sy.Plan):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.fc1 = nn.Linear(2, 3)\n",
    "        self.fc2 = nn.Linear(3, 2)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return F.log_softmax(x, dim=0)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net = Net()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos construir um plano usando dados de exemplo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net.build(torch.tensor([1., 2.]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Agora enviamos o Plano para um worker remoto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_net = net.send(device_1)\n",
    "pointer_to_net"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos recuperar alguns dados remotos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_data = device_1.search('input_data')[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A sintaxe é como a execução sequencial remota, isso é, como uma execução local. Mas comparada a uma execução remota clássica, tem apenas uma rodada de comunicação para cada execução."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_result = pointer_to_net(pointer_to_data)\n",
    "pointer_to_result"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "E recebemos o resultado como sempre!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_result.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Et voilà! Nós vimos como dramaticamente reduzir a comunicação entre worker local (ou servidor) e os dispositivos remotos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Troca entre Workers\n",
    "\n",
    "Uma característica importante que queremos ter é usar o mesmo plano para vários workers, que podemos mudar dependendo da parte remota do dado que estamos considerando.\n",
    "Em especial, não queremos reconstruir o plano cada vez que temos que mudar de worker. Vamos ver como fazer isso usando o exemplo visto anteriormente com a nossa pequena rede."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Net(sy.Plan):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.fc1 = nn.Linear(2, 3)\n",
    "        self.fc2 = nn.Linear(3, 2)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return F.log_softmax(x, dim=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net = Net()\n",
    "\n",
    "# Build plan\n",
    "net.build(torch.tensor([1., 2.]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aqui são executados os passos principais"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_net_1 = net.send(device_1)\n",
    "pointer_to_data = device_1.search('input_data')[0]\n",
    "pointer_to_result = pointer_to_net_1(pointer_to_data)\n",
    "pointer_to_result.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "E você pode construir outro _PointerPlan_ a partir do mesmo plano, sendo a sintaxe a mesma para executar o plano remotamente em outro dispositivo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_net_2 = net.send(device_2)\n",
    "pointer_to_data = device_2.search('input_data')[0]\n",
    "pointer_to_result = pointer_to_net_2(pointer_to_data)\n",
    "pointer_to_result.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> Note: Atualmente, com a classe Plan, você pode apenas usar um único método e tem que nomear como \"forward\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Construindo planos automaticamente que são funções\n",
    "\n",
    "Para funções (`@` `sy.func2plan`) nós podemos automaticamente construir um plano sem e necessidada de explicitar a chamada do `build`, uma vez que no momento da criação, o plano já é criado.\n",
    "\n",
    "Para usar essa funcionalidade, a única coisa que você deve mudar na criação do plano é definir um argumento para o decorator chamado `args_shape` que deve ser uma lista que contém os shapes de cada argumento."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@sy.func2plan(args_shape=[(-1, 1)])\n",
    "def plan_double_abs(x):\n",
    "    x = x + x\n",
    "    x = torch.abs(x)\n",
    "    return x\n",
    "\n",
    "plan_double_abs.is_built"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "O parâmetro `args_shape` é usado internamente para criar tensores de exemplo com o formato no qual foi passado para construir o plano."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@sy.func2plan(args_shape=[(1, 2), (-1, 2)])\n",
    "def plan_sum_abs(x, y):\n",
    "    s = x + y\n",
    "    return torch.abs(s)\n",
    "\n",
    "plan_sum_abs.is_built"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Também é possível prover estado de elementos para funções!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@sy.func2plan(args_shape=[(1,)], state=(torch.tensor([1]), ))\n",
    "def plan_abs(x, state):\n",
    "    bias, = state.read()\n",
    "    x = x.abs()\n",
    "    return x + bias"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_plan = plan_abs.send(device_1)\n",
    "x_ptr = torch.tensor([-1, 0]).send(device_1)\n",
    "p = pointer_plan(x_ptr)\n",
    "p.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para aprender mais sobre isso, você pode descobrir como usar Planos com Protocolos no tutorial Parte 8 bis!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Dê-nos uma estrela em nosso repo do PySyft no GitHub\n",
    "\n",
    "A maneira mais fácil de ajudar nossa comunidade é adicionando uma estrela nos nossos repositórios! Isso ajuda a aumentar a conscientização sobre essas ferramentas legais que estamos construindo.\n",
    "\n",
    "- [Star PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Veja nossos tutoriais no GitHub!\n",
    "\n",
    "Fizemos tutoriais muito bons para entender melhor como deve ser a Aprendizagem Federada e a proteção de Privacidade, e como estamos construindo as coisas básicas que precisamos para fazer com que isso aconteça.\n",
    "\n",
    "- [Tutoriais do PySyft](https://github.com/OpenMined/PySyft/tree/master/examples/tutorials)\n",
    "\n",
    "### Junte-se ao Slack!\n",
    "\n",
    "A melhor maneira de manter-se atualizado sobre os últimos avanços é se juntar à nossa comunidade! \n",
    "\n",
    "- [http://slack.openmined.org](http://slack.openmined.org)\n",
    "\n",
    "### Contribua com o projeto!\n",
    "\n",
    "A melhor maneira de contribuir para a nossa comunidade é se tornando um contribuidor do código! A qualquer momento, você pode acessar a página de *Issues* (problemas) do PySyft no GitHub e filtrar por \"Projetos\". Isso mostrará todas as etiquetas (tags) na parte superior, com uma visão geral de quais projetos você pode participar! Se você não deseja ingressar em um projeto, mas gostaria de codificar um pouco, também pode procurar mais mini-projetos \"independentes\" pesquisando problemas no GitHub marcados como \"good first issue\".\n",
    "\n",
    "- [Etiquetados como Good First Issue](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Doar\n",
    "\n",
    "Se você não tem tempo para contribuir com nossa base de códigos, mas ainda deseja nos apoiar, também pode se tornar um Apoiador em nosso Open Collective. Todas as doações vão para hospedagem na web e outras despesas da comunidade, como hackathons e meetups!\n",
    "\n",
    "[Página do Open Collective do OpenMined](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5rc1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
